给定正整数 n,返回在 _ _[1, n] _ _ 范围内具有 至少 1 位  重复数字的正整数的个数。
示例 1: 
**输入:** n = 20
**输出:** 1
**解释:** 具有至少 1 位重复数字的正数(<= 20)只有 11 。
示例 2: 
**输入:** n = 100
**输出:** 10
**解释:** 具有至少 1 位重复数字的正数(<= 100)有 11,22,33,44,55,66,77,88,99 和 100 。
示例 3: 
**输入:** n = 1000
**输出:** 262
提示: 
前言 关于数位 DP 的详细介绍可以参考「数位DP(OI Wiki)  」,类似题目有「233. 数字 1 的个数  」、「600. 不含连续1的非负整数  」等。
方法一:记忆化搜索 由互斥原理可知,至少有 1 位重复的数字的正整数个数等于总个数减去没有重复数字的正整数个数。为了方便计算,我们首先求出在整数区间 [0, n] 之间的没有重复数字的正整数个数 x,那么结果等于 n+1-x。
我们从最高位开始填入各个数字,使用整数掩码 mask 记录前面已经填入过的数字(注意前缀 0 不计入已填入的数字)。假设当前填入第 i 位,如果前面填入的数字与 n 对应位置的数字相同,那么可选的填入数字小于等于 n 在第 i 位的数字,否则可填入全部数字。
记可填入的最大数字为 t,依次尝试填入数字 k \in [0, t],如果 k 已经出现在 mask 中,那么说明填入数字 k 不合法,否则说明可以填入数字 k,那么尝试填入第 i+1 位的数字。
在填入第 i+1 位的数字时,如果掩码 mask}i = 0 且 k=0 成立,那么说明前面都是前缀 0,掩码 mask} {i+1 为 0,否则 mask}_{i+1 等于 mask}_i 在第 k 位设为一后的值。如果在填入第 i 位时,前面填入的数字与 n 对应位置的数字相同,且在第 i 位填入的数字为 t,那么填入第 i+1 位时,前面填入的数字也与 n 对应位置的数字相同。
注意到,假设当前需要填入第 i 位,且前面填入的数字与 n 对应位置的数字不相同,那么需要求得的不重复数字的正整数个数只与 mask 相关,我们可以使用备忘录 dp 记录该结果,避免重复计算。
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class  Solution :    def  numDupDigitsAtMostN (self, n: int  ) -> int :         A = list (map (int , str (n)))         N = len (A)         @cache         def  f (i, tight, mask, hasDup ):             if  i >= N:                 if  hasDup:                     return  1                  return  0              upperLimit = A[i] if  tight else  9              ans = 0              for  d in  range (upperLimit + 1 ):                 tight2 = tight and  d == upperLimit                 mask2 = mask if  mask == 0  and  d == 0  else  mask | (1  << d)                 hasDup2 = hasDup or  (mask & (1  << d))                 ans += f(i + 1 , tight2, mask2, hasDup2)             return  ans         return  f(0 , True , 0 , False ) 
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class  Solution  {public :    vector<vector<int >> dp;     int  f (int  mask, const  string &sn, int  i, bool  same)           if  (i == sn.size ()) {             return  1 ;         }         if  (!same && dp[i][mask] >= 0 ) {             return  dp[i][mask];         }         int  res = 0 , t = same ? (sn[i] - '0' ) : 9 ;         for  (int  k = 0 ; k <= t; k++) {             if  (mask & (1  << k)) {                 continue ;             }             res += f (mask == 0  && k == 0  ? mask : mask | (1  << k), sn, i + 1 , same && k == t);         }         if  (!same) {             dp[i][mask] = res;         }         return  res;     }     int  numDupDigitsAtMostN (int  n)           string sn = to_string (n);         dp.resize (sn.size (), vector <int >(1  << 10 , -1 ));         return  n + 1  - f (0 , sn, 0 , true );     } }; 
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class  Solution  {    int [][] dp;     public  int  numDupDigitsAtMostN (int  n)  {         String  sn  =  String.valueOf(n);         dp = new  int [sn.length()][1  << 10 ];         for  (int  i  =  0 ; i < sn.length(); i++) {             Arrays.fill(dp[i], -1 );         }         return  n + 1  - f(0 , sn, 0 , true );     }     public  int  f (int  mask, String sn, int  i, boolean  same)  {         if  (i == sn.length()) {             return  1 ;         }         if  (!same && dp[i][mask] >= 0 ) {             return  dp[i][mask];         }         int  res  =  0 , t = same ? (sn.charAt(i) - '0' ) : 9 ;         for  (int  k  =  0 ; k <= t; k++) {             if  ((mask & (1  << k)) != 0 ) {                 continue ;             }             res += f(mask == 0  && k == 0  ? mask : mask | (1  << k), sn, i + 1 , same && k == t);         }         if  (!same) {             dp[i][mask] = res;         }         return  res;     } } 
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public  class  Solution  {    int [][] dp;     public  int  NumDupDigitsAtMostN (int  n         string  sn = n.ToString();         dp = new  int [sn.Length][];         for  (int  i = 0 ; i < sn.Length; i++) {             dp[i] = new  int [1  << 10 ];             Array.Fill(dp[i], -1 );         }         return  n + 1  - F(0 , sn, 0 , true );     }     public  int  F (int  mask, string  sn, int  i, bool  same         if  (i == sn.Length) {             return  1 ;         }         if  (!same && dp[i][mask] >= 0 ) {             return  dp[i][mask];         }         int  res = 0 , t = same ? (sn[i] - '0' ) : 9 ;         for  (int  k = 0 ; k <= t; k++) {             if  ((mask & (1  << k)) != 0 ) {                 continue ;             }             res += F(mask == 0  && k == 0  ? mask : mask | (1  << k), sn, i + 1 , same && k == t);         }         if  (!same) {             dp[i][mask] = res;         }         return  res;     } } 
[sol1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int  f (int  mask, const  char  *sn, int  i, bool  same, int  **dp)  {    if  (sn[i] == '\0' ) {         return  1 ;     }     if  (!same && dp[i][mask] >= 0 ) {         return  dp[i][mask];     }     int  res = 0 , t = same ? (sn[i] - '0' ) : 9 ;     for  (int  k = 0 ; k <= t; k++) {         if  (mask & (1  << k)) {             continue ;         }         res += f(mask == 0  && k == 0  ? mask : mask | (1  << k), sn, i + 1 , same && k == t, dp);     }     if  (!same) {         dp[i][mask] = res;     }     return  res; } int  numDupDigitsAtMostN (int  n)  {    char  sn[32 ];     sprintf (sn, "%d" , n);     int  len = strlen (sn);     int  *dp[len];     for  (int  i = 0 ; i < len; i++) {         dp[i] = (int  *)malloc (sizeof (int ) * (1  << 10 ));         memset (dp[i], 0xff , sizeof (int ) * (1  << 10 ));     }     int  ret = n + 1  - f(0 , sn, 0 , true , dp);     for  (int  i = 0 ; i < len; i++) {         free (dp[i]);     }     return  ret; } 
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 var  numDupDigitsAtMostN = function (n ) {    const  sn = ''  + n;     dp = new  Array (sn.length ).fill (0 ).map (() =>  new  Array (1  << 10 ).fill (-1 ));     const  f  = (mask, sn, i, same ) => {         if  (i === sn.length ) {             return  1 ;         }         if  (!same && dp[i][mask] >= 0 ) {             return  dp[i][mask];         }         let  res = 0 , t = same ? (sn[i].charCodeAt () - '0' .charCodeAt ()) : 9 ;         for  (let  k = 0 ; k <= t; k++) {             if  ((mask & (1  << k)) !== 0 ) {                 continue ;             }             res += f (mask === 0  && k === 0  ? mask : mask | (1  << k), sn, i + 1 , same && k === t);         }         if  (!same) {             dp[i][mask] = res;         }         return  res;     };     return  n + 1  - f (0 , sn, 0 , true ); } 
复杂度分析 
方法二:组合数学 方法一只有两种情况需要继续进入搜索:
其他情况可以直接利用组合数学进行计算,当前填入第 i 位,那么剩余 m - 1 - i 位待填入,记已经填入的数字数目为 c,那么可选的数字数目为 10-c,那么剩余的不重复数字的正整数个数等于组合数 A^{m - 1 - i}_{10-c(n 的数据范围保证了组合数合法)。
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 class  Solution :    def  numDupDigitsAtMostN (self, N: int  ) -> int :         limit, s = list (map (int , str (N + 1 ))), set ()         n, res = len (limit), sum (9  * perm(9 , i) for  i in  range (len (limit) - 1 ))         for  i, x in  enumerate (limit):             for  y in  range (i == 0 , x):                 if  y not  in  s:                     res += perm(9  - i, n - i - 1 )             if  x in  s:                  break              s.add(x)         return  N - res 
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class  Solution  {public :    int  A (int  x, int  y)           int  res = 1 ;         for  (int  i = 0 ; i < x; i++) {             res *= y--;         }         return  res;     }     int  f (int  mask, const  string &sn, int  i, bool  same)           if  (i == sn.size ()) {             return  1 ;         }         int  t = same ? sn[i] - '0'  : 9 , res = 0 , c = __builtin_popcount(mask) + 1 ;         for  (int  k = 0 ; k <= t; k++) {             if  (mask & (1  << k)) {                 continue ;             }             if  (same && k == t) {                 res += f (mask | (1  << t), sn, i + 1 , true );             } else  if  (mask == 0  && k == 0 ) {                 res += f (0 , sn, i + 1 , false );             } else  {                 res += A (sn.size () - 1  - i, 10  - c);             }         }         return  res;     }     int  numDupDigitsAtMostN (int  n)           string sn = to_string (n);         return  n + 1  - f (0 , sn, 0 , true );     } }; 
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class  Solution  {    public  int  numDupDigitsAtMostN (int  n)  {         String  sn  =  String.valueOf(n);         return  n + 1  - f(0 , sn, 0 , true );     }     public  int  f (int  mask, String sn, int  i, boolean  same)  {         if  (i == sn.length()) {             return  1 ;         }         int  t  =  same ? sn.charAt(i) - '0'  : 9 , res = 0 , c = Integer.bitCount(mask) + 1 ;         for  (int  k  =  0 ; k <= t; k++) {             if  ((mask & (1  << k)) != 0 ) {                 continue ;             }             if  (same && k == t) {                 res += f(mask | (1  << t), sn, i + 1 , true );             } else  if  (mask == 0  && k == 0 ) {                 res += f(0 , sn, i + 1 , false );             } else  {                 res += A(sn.length() - 1  - i, 10  - c);             }         }         return  res;     }     public  int  A (int  x, int  y)  {         int  res  =  1 ;         for  (int  i  =  0 ; i < x; i++) {             res *= y--;         }         return  res;     } } 
[sol2-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 int  A (int  x, int  y)  {    int  res = 1 ;     for  (int  i = 0 ; i < x; i++) {         res *= y--;     }     return  res; } int  f (int  mask, const  char  *sn, int  i, bool  same)  {    if  (sn[i] == '\0' ) {         return  1 ;     }     int  t = same ? sn[i] - '0'  : 9 , res = 0 , c = __builtin_popcount(mask) + 1 ;     for  (int  k = 0 ; k <= t; k++) {         if  (mask & (1  << k)) {             continue ;         }         if  (same && k == t) {             res += f(mask | (1  << t), sn, i + 1 , true );         } else  if  (mask == 0  && k == 0 ) {             res += f(0 , sn, i + 1 , false );         } else  {             res += A(strlen (sn) - 1  - i, 10  - c);         }     }     return  res; } int  numDupDigitsAtMostN (int  n) {    char  sn[32 ];     sprintf (sn, "%d" , n);     return  n + 1  - f(0 , sn, 0 , true ); } 
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 var  numDupDigitsAtMostN = function (n ) {    const  sn = ''  + n;     const  f  = (mask, sn, i, same ) => {         if  (i === sn.length ) {             return  1 ;         }         let  t = same ? sn[i].charCodeAt () - '0' .charCodeAt () : 9 , res = 0 , c = bitCount (mask) + 1 ;         for  (let  k = 0 ; k <= t; k++) {             if  ((mask & (1  << k)) !== 0 ) {                 continue ;             }             if  (same && k === t) {                 res += f (mask | (1  << t), sn, i + 1 , true );             } else  if  (mask === 0  && k === 0 ) {                 res += f (0 , sn, i + 1 , false );             } else  {                 res += A (sn.length  - 1  - i, 10  - c);             }         }         return  res;     }     return  n + 1  - f (0 , sn, 0 , true ); } const  A  = (x, y ) => {    let  res = 1 ;     for  (let  i = 0 ; i < x; i++) {         res *= y--;     }     return  res; } const  bitCount  = (n ) => {    return  n.toString (2 ).split ('0' ).join ('' ).length ; } 
复杂度分析